import VNode, { cloneVNode } from './vnode';
import {
    warn,
    isDef,
    isUndef,
    isTrue,
    makeMap,
    isRegExp,
    isPrimitive
} from '../uitl/index'
import { isTextInputType } from '../../platforms/web/util/element';
import * as nodeOps from '../../platforms/web/runtime/node-ops'
// 检查两个节点是否相似
/**
 * 
 * @param {VNode} a 
 * @param {VNode} b 
 * @returns 
 */
function sameVnode(a,b){
    return (
        a.key === b.key && (
            (
                a.tag === b.tag &&
                a.isComment === a.isComment &&
                isDef(a.data) === isDef(b.data) &&
                sameInputType(a, b)
            ) || (
                isTrue(a.isAsyncPlaceholder) && 
                a.asyncFactory === b.asyncFactory &&
                isUndef(b.asyncFactory.error)
            )
        )
    )
}

// 判断两个文本框节点type属性是否相等或者均为输入框
/**
 * 
 * @param {VNode} a 
 * @param {VNode} b 
 * @returns 
 */
function sameInputType(a, b){
    // 如果a不是input标签，则直接返回true
    if(a.tag !== 'input') return true
    let i
    const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
    const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
    return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)  
}

// 创建一个 key - oldIndex 的对象
/**
 * 改函数会遍历children中beginIdx到endIdx中的vnode
 * 如果vnode中key有值，则以key-index键值对的形式将其保存并返回
 * @param {Array<VNode>} children 
 * @param {number} beginIdx 
 * @param {number} endIdx 
 * @returns 
 */
function createKeyToOldIdx(children, beginIdx, endIdx){
    let i, key
    const map = {}
    for(i = beginIdx; i <= endIdx; ++i){
        key = children[i].key
        if(isDef(key)) map[key] = i;
    }
    return map;
}

/**
 * @namespace
 * @param {object} backend
 * @param {object} backend.nodeOps
 */
export function createPatchFunction(backend){

    const {nodeOps} = backend

    function insert(parent, elm, ref){
        if(isDef(parent)){
            // 判断是否有参考节点
            if(isDef(ref)){
                // 判断ref的父节点是否为parent
                if(nodeOps.parentNode(ref) === parent){
                    // 将节点插入到ref的前面
                    nodeOps.insertBefore(parent, elm ,ref);
                }
            }
            // 没有参考节点直接插入
            else{
                nodeOps.appendChild(parent,elm);
            }
        }
    }

    /**
     * 
     * @param {VNode} vnode - 虚拟DOM
     * @param {Node} parentElm -父节点
     * @param {Node} refElm - 参考节点
     * @param {Array<VNode>} ownerArray - vnode所在的数组
     * @param {number} index - vnode在数组中的位置
     * @param {boolean} nested -是否是嵌套的节点
     */
    // 创建一个节点
    function createElm(vnode,insertedVnodeQueue,parentElm,refElm,nested, ownerArray, index){
        // 判断vnode以前是否使用过，如果使用过，则重新copy一个
        if(isDef(vnode.elm) && isDef(ownerArray)){
            vnode = ownerArray[index] = cloneVNode(vnode);
        }

        vnode.isRootInsert = !nested;
        const data = vnode.data;
        const children = vnode.children;
        const tag = vnode.tag;
        // 判断是否存在tag属性，如果存在，则表示该vnode是一个元素节点
        if(isDef(tag)){
            vnode.elm = nodeOps.createElement(tag, vnode);
            createChildren(vnode, children, insertedVnodeQueue);
            insert(parentElm, vnode.elm, refElm);
        }
        // 判断vnode.isComment是否为true，为true则为注释节点
        else if(isTrue(vnode.isComment)){
            vnode.elm = nodeOps.createComment(vnode.text)
            insert(parentElm,vnode.elm,refElm)
        }
        // 文本节点
        else {
            vnode.elm = nodeOps.createTextNode(vnode.text)
            insert(parentElm,vnode.elm,refElm)
        }
    }

    // 删除一个节点
    function removeNode(el){
        const parent = nodeOps.parentNode(el);
        if(isDef(parent)){
            nodeOps.removeChild(parent, el);
        }
    }

    /**
     * 添加多个节点
     * @param {Node} parentElm 
     * @param {*} insertedVnodeQueue 
     * @param {Array<VNode>} newCh 
     * @param {Node} refElm 
     * @param {number} newStartIdx 
     * @param {number} newEndIdx 
     */
    // 添加多个节点
    function addVnodes(parentElm, insertedVnodeQueue,  newCh, refElm,newStartIdx,newEndIdx){
        for(;newStartIdx <= newEndIdx; newStartIdx++){
            createElm(newCh[newStartIdx], insertedVnodeQueue, parentElm, refElm, false, newCh, newStartIdx)
        }
    }

    /**
     * 
     * @param {Array<VNode>} oldCh 
     * @param {number} startIdx 
     * @param {number} endIdx 
     */
    function removeVnodes(oldCh, startIdx, endIdx){
        for(;startIdx <= endIdx; startIdx++){
            let c = oldCh[startIdx];
            if(isDef(c)){
                if(isDef(c.tag)){
                    
                }else{// Text node
                    removeNode(c.elm);
                }
            }
        }
    }

    /**
     * 
     * @param {VNode} oldVnode - 旧节点
     * @param {VNode} vnode - 新节点
     * @param {*} insertedVnodeQueue 
     * @param {Array<VNode>} ownerArray - vnode所在的数组，拥有vnode的数组
     * @param {number} index - vnode在ownerArray中的索引
     * @param {*} removeOnly - 这是一个特殊的标志，仅用于<transition-group>
     * @returns 
     */

    // 比较两个节点
    function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly){
        // 判断vnode和oldVnode是否一致
        if(oldVnode === vnode){
            return
        }
        // 判断新节点是否被使用过，使用过则将该vdnome进行克隆，以免造成影响
        if (isDef(vnode.elm) && isDef(ownerArray)) {
            // clone reused vnode
            vnode = ownerArray[index] = cloneVNode(vnode)
        }
      
        // vnode为新节点，此时是没有elm属性的，oldVnode为旧节点
        // 能够进入patchVnode的新旧节点，一定经过了sameVnode方法的判断，说明他们节点本身几乎一样
        // 所以新节点可以使用旧的节点
        const elm = vnode.elm = oldVnode.elm

        // 判断是否为静态节点 , key值是否相等，新值是否为克隆节点或者具有isOnce属性
        // 如果条件成立，则不更新
        if(isTrue(vnode.isStatic) && 
            isTrue(oldVNode.isStatic) && 
            vnode.key === oldVnode.key && 
            (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
            ){
            return
        }

        const oldCh = oldVnode.children
        const ch = vnode.children
        // 判断vnode是否具有text属性
        // 没有则说明
        // 1. vNode是个文本节点，但文本节点的text为假值
        // 2. vnode有children子节点
        if(isUndef(vnode.text)){
            // 判断vnode和oldVnode是否都存在子节点
            if(isDef(vnode.children) && isDef(oldVnode.children)){
                // 若存在，则判断子节点是否相同,不同则更新子节点
                if(oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly);
            }
            // 若只有vnode的子节点存在
            else if(isDef(ch)){
                /** 
                 * 判断oldVnode中是否有文本
                 * 若有，则清空原本节点中的文本，再将子节点插入
                 * 若没有，则直接插入子节点
                */
               if(isDef(oldVnode.text)) nodeOps.setTextContent(elm, '');
               addVnodes(elm,null,ch,0,ch.length - 1, insertedVnodeQueue);
            }
            // 若只有oldVnode的子节点存在
            else if(isDef(oldCh)){
                /**
                 * 若只有oldVnode的节点存在
                 * 则直接清空子节点
                 */
                removeVnodes(elm, oldCh, 0, oldCh.length - 1)
            }
            // 若vnode和oldVnode都没有子节点，但oldVnode中有文本(即是文本节点)
            else if(isDef(oldVnode.text)){
                // 清空文本
                nodeOps.setTextContent(elm, '');
            }
        }
        // 若vnode有文本，则判断vnode的文本和oldVnode的文本是否相等，如果不相等，才进行更新
        else if(oldVnode.text !== vnode.text){
            nodeOps.setTextContent(elm, vnode.text);
        }
    }

    /**
     * 
     * @param {Node} parentElm 
     * @param {Array<VNode>} oldCh 
     * @param {Array<VNode>} newCh 
     * @param {*} insertedVnodeQueue 
     * @param {boolean} removeOnly - 这是一个特殊的标志，仅用于<transition-group>
     */
    // diff子节点进行更新
    function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly){
        let oldStartIdx = 0; // 旧节点列表的起始索引
        let newStartIdx = 0; // 新节点列表的起始索引
        let oldEndIdx = oldCh.length - 1; // 旧节点列表结束索引
        let newEndIdx = newCh.length - 1; // 新节点列表的结束索引
        let oldStartVnode = oldCh[0]; // oldCh中未处理节点中的第一个
        let oldEndVnode = oldCh[oldEndIdx]; // oldCh中未处理节点中的最后一个
        let newStartVnode = newCh[0]; // newCh中未处理节点中的第一个
        let newEndVNode = newCh[newEndIdx]; // newCh中未处理节点中的最后一个

        // oldKeyToIdx 用于存储oldCh中vnode的key-index键值对
        // idxInOld 存储和当前比对的newStartVNode相似的oldVnode索引
        // vnodeToMove 存储需要移动的oldVnode，与idxInOld对应
        // refElm 循环完进行比对的时候用于存储
        let oldKeyToIdx, idxInOld, vnodeToMove, refElm;
        
        // removeOnly is a special flag used only by <transition-group>
        // to ensure removed elements stay in correct relative positions
        // during leaving transitions
        // removeOnly是一个特殊的标志，仅用于<transition-group>
        // 确保删除的元素在离开转换期间保持在正确的相对位置
        const canMove = !removeOnly;

        // 当两者其一遍历完成时，结束遍历
        // 如果oldCh先遍历完毕，则将newCh未遍历的节点插入进oldCh
        // 如果newCh先遍历完毕，则将oldCh未遍历的节点删除
        while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx){
            // 检查oldStartVnode是否已经处理过为undefined了
            if(isUndef(oldStartVnode)){
                // 获取下一个未处理过的节点
                oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
            }
            // 如果最后位置的旧节点被处理了,向右移动
            else if(isUndef(oldEndVnode)){
                oldEndVnode = oldCh[--oldEndIdx]; 
            }
            // 判断oldCh和newCh中第一个未处理的节点是否相似
            else if(sameVnode(oldStartVnode, newStartVnode)){
                // 如果相似，则进行patch
                patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue,newCh, newStartIdx);
                oldStartVnode = oldCh[++oldStartIdx];
                oldStartVnode = newCh[++newStartIdx];
            }
            // 判断oldCh和newCh中最后一个未处理的节点是否相似
            else if(sameVnode(oldEndVnode, newEndVNode)){
                patchVnode(oldEndVnode, newEndVNode, insertedVnodeQueue,newCh, newEndIdx);
                oldEndVnode = oldCh[--oldEndIdx];
                newEndVNode = newCh[--newEndIdx];
            }
            // 判断oldCh未处理的第一个vdom和newCh未处理的最后一个是否相似
            // 如果相似，将其patch之后将其移动到所有未处理的节点之后
            else if(sameVnode(oldStartVnode, newEndVNode)){ // Vnode moved right
                patchVnode(oldStartVnode, newEndVNode, insertedVnodeQueue,newCh, newEndIdx);
                canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
                oldStartVnode = oldCh[++oldStartIdx];
                newEndVNode = newCh[--newEndIdx];
            }
            // 判断oldEndVnode和newStartVnode
            // 如果相似，将其移动到所有未处理的节点之前
            else if(sameVnode(oldEndVnode, newStartVNode)){ // Vnode moved left
                patchVnode(oldEndVnode, newStartVNode, insertedVnodeQueue,newCh, newStartIdx);
                canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
                oldEndVnode = oldCh[--oldEndIdx];
                newStartVnode = newCh[++newStartIdx];
            }
            // 如果以上几个条件都不满足，则进行普通遍历patch
            else {
                // 如果oldKeyToIdx没有赋值，则代表没有执行过createKeyToOldIdx函数
                if(isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx,oldEndIdx);

                // 判断新前是否存在key,如果存在，则通过oldKeyToIdx获取该key值在oldCh中的位置
                // 如果不存在，则通过findIdxInOld函数进行遍历查找oldCh中在oldStartIdx到oldEndIdx之间与
                // newStartVnode相似的oldVnode
                idxInOld = isDef(newStartVnode.key)
                ? oldKeyToIdx[newStartVnode.key]
                : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
                // 如果idxInOld没值，则代表oldCh中不存在与newStartVnode相似的节点，需要重新创建一个新的节点
                // 并将其插入到未处理的节点之前
                if(isUndef(idxInOld)){ // New element
                    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
                }
                // 如果存在
                else{
                    vnodeToMove = oldCh[idxInOld]
                    // 判断是否相似（就算key值一样，也不代表这两个节点相似，所以需要重新检查一遍）
                    if(sameVnode(vnodeToMove, newStartVnode)){
                        patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh,newStartIdx);
                        // 将变换过的vnodeToMove在oldCh中转换为undefined，代表已经转移过了
                        oldCh[idxInold] = undefined;
                        // 将节点进行移动,将节点插入到未处理的节点之前
                        canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm);
                    }
                    // 如果不相似，则代表新前，旧前只有key值一致，作为新元素进行处理
                    // 将其插入到未处理的节点之前
                    else{
                        // same key but different element. treat as new element
                        createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
                    }
                }
                // newVode moved left
                newStartVnode = newCh[++newStartIdx];
            }
        }

        // 循环结束后，判断oldStartIdx和oldEndIdx
        // 如果oldStartIdx > oldEndIdx
        // 则代表oldCh遍历完成,接着再检测newCh是否遍历完毕
        // 如果遍历完了，则代表此次更新完毕
        // 如果没遍历完，则将剩下的节点都作为新元素处理，将其插入到
        if(oldStartIdx > oldEndIdx){
            // newEndIdx的vnode不是为null就是一个已经处理好的节点，剩下的[newStartIdx,newEndIdx]之间的节点全部插入到它之前
            refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1];
            addVnodes(parentElm,insertedVnodeQueue, newCh, refElm, newStartIdx, newEndIdx);
        }
        // 如果newCh先遍历完
        else if(newStartIdx > newEndIdx){
            removeVnodes(oldCh, oldStartIdx, oldEndIdx);
        }
        
    }

    /**
     * 该函数通过循环遍历的方式，查找oldCh在oldStartIdx到oldEndIdx之间与newVnode相似的oldVnode，
     * 并返回oldVnode在oldCh中的索引
     * @param {VNode} vnode 
     * @param {Array<VNode>} oldCh 
     * @param {number} start 
     * @param {end} end 
     * @returns 
     */
    function findIdxInOld(newVnode, oldCh, start, end){
        for (let i = start; i < end; i++){
            const c = oldCh[i];
            if(isDef(c) && sameVnode(newVnode, c)) return i         
        }
    }
}